/*
 * buffered file I/O
 * Copyright (c) 2001 Fabrice Bellard
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "libavutil/avstring.h"
#include "libavutil/opt.h"
#include "avformat.h"
#include <fcntl.h>
#if HAVE_IO_H
#include <io.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/stat.h>
#include <stdlib.h>
#include "os_support.h"
#include "url.h"

/* standard file protocol */
typedef struct ringbf_info
{
	int state;
	void* session;
}ringbf_info;

typedef struct RingbfContext {
    const AVClass *class;
	ringbf_info* rb;
//    int fd;
//    int trunc;
} RingbfContext;
#if 0
static const AVOption ringbf_options[] = {
    { "truncate", "Truncate existing files on write", offsetof(RingbfContext, trunc), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
    { NULL }
};


static const AVClass ringbf_class = {
    .class_name = "file",
    .item_name  = av_default_item_name,
    .option     = ringbf_options,
    .version    = LIBAVUTIL_VERSION_INT,
};
#endif
typedef struct ringbuffer
{
	 int maxsize;
	 int wpos;
	 int rpos;
	 int dbg_min_size;
	 int dbg_max_size;
	 int dbg_counter;
	 char* buffer;
}ringbuffer;
static int GetBufferSize(ringbuffer* rb)
{
	if( rb->wpos >= rb->rpos )
		return rb->wpos-rb->rpos;
	else
		return rb->maxsize-(rb->rpos-rb->wpos);
}
#if 0
static void DebugInfo(ringbuffer* rb)
{		
	av_log(NULL, AV_LOG_INFO,"--trace info,datasize=%d wpos=%d rpos=%d[%d]",GetBufferSize(rb),rb->wpos,rb->rpos,rb->maxsize);		
}
#endif
static int ringbuffer_Read(void* session,char* data,int size)
{
	ringbuffer* rb = (ringbuffer*)session;
	if( !rb )
		return -1;
	int datasize = GetBufferSize(rb);		
	if( datasize <= 0 )
	{
		//av_log(NULL, AV_LOG_INFO,"hint no more data %d, size=%d\r\n",datasize,size);
		//DebugInfo(rb);
		return 0;
	}
	if( size > datasize )
		return 0;
	{			
		if( rb->dbg_min_size > datasize )
			rb->dbg_min_size = datasize;
		if( rb->dbg_max_size < datasize )
			rb->dbg_max_size = datasize;
		if( rb->dbg_counter % 100 == 0 )
			av_log(NULL, AV_LOG_INFO,"debug buffer data,%d size=%d[%d,%d]\r\n",rb->dbg_counter,datasize,rb->dbg_min_size,rb->dbg_max_size);
		rb->dbg_counter++;
	}
//	if( size > datasize )
//		size = datasize;
	
	if( rb->rpos >= rb->maxsize )
		rb->rpos = 0;
	if( rb->rpos+size <= rb->maxsize )
	{
		memcpy(data,rb->buffer+rb->rpos,size);
		//av_log(NULL, AV_LOG_INFO,"Read trace,rpos=%d(%p) size=%d(%x %x %x)\r\n",rb->rpos,rb->buffer,size,rb->buffer[rb->rpos],rb->buffer[rb->rpos+1],rb->buffer[rb->rpos+2]);
		rb->rpos += size;
	}else
	{
		int n = rb->maxsize-rb->rpos;
		//av_log(NULL, AV_LOG_INFO,"Read trace2,rpos=%d size=%d\r\n",rb->rpos,n);
		memcpy(data,rb->buffer+rb->rpos,n);
		rb->rpos = size-n;
		//av_log(NULL, AV_LOG_INFO,"Read trace2,rpos=0 size=%d\r\n",rb->rpos+1);
		memcpy(data+n,rb->buffer,rb->rpos);
	}
	//DebugInfo(rb);
	return size;
}

static int ringbf_read(URLContext *h, unsigned char *buf, int size)
{
    RingbfContext *c = h->priv_data;
	if( c->rb->state )
	{
		av_log(h, AV_LOG_INFO,"read ringbuffer exit\r\n");
		return AVERROR(AVERROR_EOF);
	}
	int r = -1;
    int timeout = 0;
	while(1)
	{
	    r = ringbuffer_Read(c->rb->session, buf, size);
		if( r == -1 )
			av_log(NULL, AV_LOG_INFO,"read ringbuffer exit %d\r\n",r);
		if( r != 0 )
			break;
		usleep(1000*2);
		timeout++;
		if( timeout > 300 )
	    {
			av_log(h, AV_LOG_ERROR,"hint,ringbf_read timeout=%d %d(timeout=%d)\r\n",r,size,timeout);
			break;
	    }
	}
    return (-1 == r)?AVERROR(errno):r;
}

static int ringbf_open(URLContext *h, const char *filename, int flags)
{
    RingbfContext *c = h->priv_data;
    int access;
    int fd;
    struct stat st;

    char *final;
    av_strstart(filename, "ringbf:", &filename);

	fd = strtol(filename, &final, 16);
    if((filename == final) || *final ) {
		return -1;
    }

    c->rb = (ringbf_info*)fd;
	av_log(NULL, AV_LOG_INFO,"ringbf(%x) %x %d\r\n",c->rb,c->rb->session,c->rb->state);
    h->is_streamed = 1;
    return 0;
}

static int ringbf_close(URLContext *h)
{
    RingbfContext *c = h->priv_data;
    return 0;
}

URLProtocol ff_ringbf_protocol = {
    .name                = "ringbf",
    .url_open            = ringbf_open,
    .url_read            = ringbf_read,
    .url_close           = ringbf_close,
    .priv_data_size      = sizeof(RingbfContext),
//    .priv_data_class     = &ringbf_class,
};

